package com.lovesher.payment.service.plugin.alipay;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import com.lovesher.payment.service.utils.excel.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.lovesher.payment.common.enums.EnumAlipayTradeStatus;
import com.lovesher.payment.common.enums.NotifyMethod;
import com.lovesher.payment.common.enums.PayStatus;
import com.lovesher.payment.common.enums.PayType;
import com.lovesher.payment.common.resposity.bean.AccountRechargeDTO;
import com.lovesher.payment.common.resposity.bean.PaymentSerial;
import com.lovesher.payment.common.resposity.bean.PluginConfig;
import com.lovesher.payment.common.resposity.bean.PluginConfigAttribute;
import com.lovesher.payment.service.resposity.bean.PaymentInterface;
import com.lovesher.payment.service.resposity.dao.PaymentDao;
import com.lovesher.payment.service.resposity.dao.PaymentSerialDao;
import com.lovesher.payment.service.resposity.mapper.PluginConfigAttributeMapper;
import com.lovesher.payment.service.utils.excel.DateUtils;

/**
 * @Title: AliPayPlugin.java
 * 
 * @Package net.showcoo.plugin.alipay
 * 
 * @Description: 支付宝支付插件
 * 
 * @version V1.0
 */
@Component("aliAppPayPlugin")
public class AliAppPayPlugin extends PaymentInterface {

	private static Log log = LogFactory.getLog(AliAppPayPlugin.class);

	@Resource
	private PluginConfigAttributeMapper pluginConfigAttributeMapper;

	@Resource
	private PaymentSerialDao paymentSerialDao;
	
	@Resource
	private PaymentDao paymentDao;

	@Override
	public String getName() {
		return "支付宝支付";
	}

	@Override
	public String getVersion() {
		return "1.0";
	}

	@Override
	public String getAuthor() {
		return "hyf";
	}

	/**
	 * 获取网址
	 * 
	 * @return 网址
	 */
	@Override
	public String getSiteUrl() {
		return "https://doc.open.alipay.com";
	}

	/**
	 * 获取请求URL
	 * 
	 * @return 请求URL
	 */
	@Override
	public String getRequestUrl() {
		return "https://openapi.alipay.com/gateway.do";
	}

	/**
	 * 获取请求方法
	 * 
	 * @return 请求方法
	 */
	@Override
	public RequestMethod getRequestMethod() {
		return RequestMethod.post;
	}

	/**
	 * 获取请求字符编码
	 * 
	 * @return 请求字符编码
	 */
	@Override
	public String getRequestCharset() {
		return "UTF-8";
	}
	
	/**
	 * 使用 Map按key进行排序
	 * 
	 * @param map
	 * @return
	 */
	public static Map<String, Object> sortMapByKey(Map<String, Object> map) {
		if (map == null || map.isEmpty()) {
			return null;
		}

		Map<String, Object> sortMap = new TreeMap<String, Object>();

		sortMap.putAll(map);

		return sortMap;
	}

	/**
	 * 获取请求参数
	 * 
	 * @param sn
	 *            编号
	 * @param description
	 *            描述
	 * @param request
	 *            httpServletRequest
	 * @return 请求参数
	 */
	@Override
	public Map<String, String> getParameterMap(AccountRechargeDTO accountRechargeDTO, String serialSn) {
		log.info("aliAppPay getParameter start..."+JSONObject.toJSONString(accountRechargeDTO)+",serialSn"+serialSn);
		PluginConfig pluginConfig = getPluginConfig();
		// 数据库中获取支付相关参数
		Map<String, Object> pluginConfigMap = getPluginConfigMap(pluginConfig);

		Map<String, String> resultMap = new HashMap<String, String>();

		resultMap.put("app_id", pluginConfigMap.get("app_id").toString());
		resultMap.put("method", pluginConfigMap.get("app_method").toString());
		resultMap.put("charset", this.getRequestCharset());
		resultMap.put("timestamp", DateUtils.format(new Date()));
		resultMap.put("version", this.getVersion());
		resultMap.put("notify_url", super.getNotifyUrl(serialSn, accountRechargeDTO.getUserId(), NotifyMethod.async,
				PayType.valueOf(accountRechargeDTO.getType())));// 接收支付异步通知回调地址

		/**
		 * 业务参数
		 */
		Map<String, Object> bizContent = new HashMap<String, Object>();

		// 商品的标题/交易标题/订单标题/订单关键字等。
		bizContent.put("subject", /* accountRechargeDTO.getProdName() */"盈米科技");

		// 商户网站唯一订单号
		bizContent.put("out_trade_no", accountRechargeDTO.getTradeNo());

		// 该笔订单允许的最晚付款时间，逾期将关闭交易。1h为一个小时 1d为一天
		bizContent.put("timeout_express", "30m");

		// 订单总金额
		bizContent.put("total_amount", accountRechargeDTO.getMoney().setScale(2, BigDecimal.ROUND_HALF_UP));

		// 收款支付宝用户ID。如果该值为空，则默认为商户签约账号对应的支付宝用户ID
		bizContent.put("seller_id", "");

		// 销售产品码，商家和支付宝签约的产品码 支付宝提供
		bizContent.put("product_code", "QUICK_MSECURITY_PAY");

		resultMap.put("biz_content", JsonUtil.write2JsonStr(bizContent));

		resultMap.put("sign_type", AlipayConfig.SIGN_TYPE);

		// 构造签名字符串
		String sign = this.createSign(resultMap, pluginConfigMap);

		resultMap.put("sign", sign);
		log.info("【订单:" + accountRechargeDTO.getTradeNo() + "userId" + accountRechargeDTO.getUserId() + "】封装参数成功...");
		log.info(JSON.toJSONString(resultMap));
		return resultMap;
	}

	private Map<String, Object> getPluginConfigMap(PluginConfig pluginConfig) {
		Map<String, Object> pluginConfigMap = new HashMap<String, Object>();
		List<PluginConfigAttribute> pluginConfigAttributes = pluginConfigAttributeMapper
				.findByPluginId(pluginConfig.getId());
		for (int i = 0; i < pluginConfigAttributes.size(); i++) {
			PluginConfigAttribute pluginConfigAttribute = pluginConfigAttributes.get(i);
			pluginConfigMap.put(pluginConfigAttribute.getName(), pluginConfigAttribute.getAttributes());
		}
		return pluginConfigMap;
	}

	/**
	 * 构造签名
	 * 
	 * @param params
	 * @param pluginConfigMap 
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public String createSign(Map<String, String> params, Map<String, Object> pluginConfigMap) {
		return AliRSA.sign(params,
				pluginConfigMap.get("private_key").toString(), this.getRequestCharset());
	}

	/**
	 * 验证通知是否合法
	 * 
	 * @param sn
	 *            编号
	 * @param notifyMethod
	 *            通知方法
	 * @param request
	 *            httpServletRequest
	 * @return 通知是否合法
	 */
	@Override
	public Map<String, Object> verifyNotify(String sn, NotifyMethod notifyMethod, HttpServletRequest request,
			PaymentSerial paymentSerial) {

		PluginConfig pluginConfig = getPluginConfig();
		Map<String, Object> pluginConfigMap = getPluginConfigMap(pluginConfig);
		String sign_type = pluginConfigMap.get("sign_type").toString();
		String partner = pluginConfigMap.get("seller_id").toString(); // partner和seller_id相同
		String alipay_public_key = pluginConfigMap.get("alipay_public_key").toString();
		
		Map<String, Object> returnNotify = new HashMap<String, Object>();

		// 通知校验ID
		String notify_id = request.getParameter("notify_id");
		// 获取远程服务器ATN结果,验证返回URL
		String veNoid = AlipayNotify.verifyResponse(notify_id, partner);
		if (veNoid.equals("invalid")) {
			returnNotify.put("success", false);
			returnNotify.put("returnText", "命令参数不对 出现这个错误，请检测返回处理中partner和key是否为空");
			log.error("命令参数不对 出现这个错误，请检测返回处理中partner和key是否为空");
			return returnNotify;
		} else if (veNoid.equals("false")) {
			returnNotify.put("success", false);
			returnNotify.put("returnText", "请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟");
			log.error("请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟");
			return returnNotify;
		}

		// 签名
		String sign = request.getParameter("sign");
		
		Map<String, Object> velMap = new HashMap<String, Object>();
		@SuppressWarnings("rawtypes")
		Enumeration enu = request.getParameterNames();

		while (enu.hasMoreElements()) {
			String paraName = (String) enu.nextElement();
			velMap.put(paraName, request.getParameter(paraName));

		}
		if (!AlipayNotify.getSignVeryfy(velMap, sign, sign_type, alipay_public_key)) {
			returnNotify.put("success", false);
			returnNotify.put("returnText", "参数验签失败");
			log.error("参数验签失败");
			return returnNotify;
		}

		String out_trade_no = request.getParameter("out_trade_no");
		// 商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号
		if (!sn.equals(out_trade_no)) {
			returnNotify.put("success", false);
			returnNotify.put("returnText", "不是商户系统中创建的订单号");
			log.error("不是商户系统中创建的订单号");
			return returnNotify;
		}

		// 订单金额
		String total_amount = request.getParameter("total_amount");

		if (new BigDecimal(total_amount).compareTo(paymentSerial.getMoney()) != 0) {
			returnNotify.put("success", false);
			returnNotify.put("returnText", "不为该订单的实际金额");
			log.error("不为该订单的实际金额");
			return returnNotify;
		}

		// 卖家支付宝用户号
		String seller_id = request.getParameter("seller_id");
		if (!pluginConfigMap.get("seller_id").equals(seller_id)) {
			returnNotify.put("success", false);
			returnNotify.put("returnText", "不为这笔单据的对应的操作方");
			return returnNotify;
		}

		String app_id = request.getParameter("app_id");
		if (!app_id.equals(pluginConfigMap.get("app_id").toString())) {
			returnNotify.put("success", false);
			returnNotify.put("returnText", "不为该商户本身");
			return returnNotify;
		}

		returnNotify.put("success", true);
		returnNotify.put("returnText", "验签成功");
		return returnNotify;
	}

	/**
	 * 获取通知返回消息
	 * 
	 * @param sn
	 *            编号
	 * @param notifyMethod
	 *            通知方法
	 * @param request
	 *            httpServletRequest
	 * @return 通知返回消息
	 */
	@Override
	public String getNotifyMessage(String sn, NotifyMethod notifyMethod, HttpServletRequest request) {
		if (notifyMethod == NotifyMethod.async) {
			return "success";
		}
		return "failure";
	}

	/**
	 * 获取超时时间
	 * 
	 * @return 超时时间
	 */
	@Override
	public Integer getTimeout() {
		return 21600;
	}

	/**
	 * 充值支付回调验证成功处理方法
	 * 
	 * @param request
	 */
	@Override
	public void paySucess(PaymentSerial paymentSerial, String returnText, PayStatus payStatus,
			HttpServletRequest request) {
		String trade_status = request.getParameter("trade_status");
		if(null != trade_status && trade_status.equals(EnumAlipayTradeStatus.TRADE_SUCCESS.getName()) && payStatus == PayStatus.SUCCESS){
			payStatus = PayStatus.SUCCESS;
		}else if(payStatus == PayStatus.SUCCESS){
			payStatus = PayStatus.FAILURE;
		}
		paymentDao.handle(paymentSerial, returnText, payStatus);
	}

	@Override
	public Boolean queryOrderResult(PaymentSerial paymentSerial) {
		// 根据充值流水号查询支付状态
		if (null != paymentSerial && paymentSerial.getStatus() == PayStatus.INIT.getValue()) {
			PluginConfig pluginConfig = getPluginConfig();
			// 数据库中获取支付相关参数
			Map<String, Object> pluginConfigMap = getPluginConfigMap(pluginConfig);
			log.info("【订单查询:" + paymentSerial.getOrderSn() + "userId" + paymentSerial.getUserId() + "】开始封装参数...");
			String appId = pluginConfigMap.get("app_id").toString();
			String charset = this.getRequestCharset();
			String signType = pluginConfigMap.get("sign_type").toString();
			String privateKey = pluginConfigMap.get("private_key").toString();
			String publicKey = pluginConfigMap.get("alipay_public_key").toString();

			AlipayClient client = new DefaultAlipayClient(getRequestUrl(), appId, privateKey, AlipayConfig.FORMAT,
					charset, publicKey, signType);
			AlipayTradeQueryRequest alipay_request = new AlipayTradeQueryRequest();

			AlipayTradeQueryModel model = new AlipayTradeQueryModel();
			model.setOutTradeNo(paymentSerial.getTradeNo());
			alipay_request.setBizModel(model);

			try {
				AlipayTradeQueryResponse response = client.execute(alipay_request);
				log.info("tradeNo："+paymentSerial.getOrderSn()+"  message："+JSON.toJSONString(response));
				if (response.isSuccess()) {
					if(response.getCode().equals("10000") && response.getTradeStatus().equals(EnumAlipayTradeStatus.TRADE_SUCCESS.getName())){
						paymentDao.handle(paymentSerial, response.getBody(), PayStatus.SUCCESS);
					}else{
						log.info("tradeNo："+paymentSerial.getOrderSn()+"  message："+JSON.toJSONString(response));
					}
				} else {
					if(response.getCode().equals("40004")){ //交易不存在
						paymentDao.handle(paymentSerial, response.getSubMsg(), PayStatus.FAILURE);
					}
				}
			} catch (AlipayApiException e) {
				log.error(e.getMessage(), e);
				return false;
			}
		}
		return true;
	}

}
